﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Numerics;
using LispInterpretor.HelperClasses;
using System.Text.RegularExpressions;

namespace LispInterpretor.DataTypes
{
    public struct Number
    {
        private const int DEFAULT_DECIMALEXPANSION = 20;

        private BigInteger _numerator;
        public BigInteger Numerator
        {
            get
            {
                return _numerator;
            }
        }

        private BigInteger _denominator;
        public BigInteger Denominator
        {
            get
            {
                return _denominator;
            }
        }

        public Number(BigInteger integer)
        {
            _numerator = integer;
            _denominator = 1;
        }

        public Number(BigInteger numerator, BigInteger denominator)
        {
            BigInteger commonDivisor = BigInteger.GreatestCommonDivisor(numerator, denominator);

            while ( commonDivisor != 1)
            {
                numerator = numerator / commonDivisor;
                denominator = denominator / commonDivisor;

                commonDivisor = BigInteger.GreatestCommonDivisor(numerator, denominator);
            }

            _numerator = numerator;
            _denominator = denominator;
        }

        public static bool IsNumber(string number)
        {
            Regex numberPattern = new Regex(@"^[0-9]+(\.[0-9]+)?$");

            return numberPattern.IsMatch(number);
        }
        public static Number Parse(string number)
        {
            Check.IsTrue(IsNumber(number), "The number passed is malformed.");

            BigInteger denominator = 1;
            BigInteger multiplier = 1;

            StringBuilder builder = new StringBuilder();

            foreach (char item in number)
            {
                denominator *= multiplier;

                if (item == '.')
                {
                    multiplier = 10;
                }
                else
                {
                    builder.Append(item);
                }
            }

            BigInteger numerator = BigInteger.Parse(builder.ToString());

            return new Number(numerator, denominator);
        }

        public static Number Add(Number left, Number right)
        {
            BigInteger newNumerator = (left.Numerator * right.Denominator) + (right.Numerator * left.Denominator);

            BigInteger newDenominator = left.Denominator * right.Denominator;

            return new Number(newNumerator, newDenominator);
        }

        public static Number Subtract(Number left, Number right)
        {
            return Add(left, new Number(-right.Numerator, right.Denominator));
        }

        public static Number Multiply(Number left, Number right)
        {
            BigInteger newNumerator = (left.Numerator * right.Numerator);
            BigInteger newDenominator = (left.Denominator * right.Denominator);

            return new Number(newNumerator, newDenominator);
        }

        public static Number Divide(Number left, Number right)
        {
            Number swappedFraction = new Number(right.Denominator, right.Numerator);

            return Multiply(left, swappedFraction);
        }

        public override string ToString()
        {
            return ToString(DEFAULT_DECIMALEXPANSION);
        }

        public string ToString(int decimalPoints)
        {
            if (Denominator.IsOne)
            {
                return Numerator.ToString();
            }
            else
            {

                return ComputeDecimalExpansion(decimalPoints);
            }
        }

        private string ComputeDecimalExpansion(int decimalPoints)
        {
            string numerator = Numerator.ToString();

            StringBuilder expansion = new StringBuilder();

            if (Numerator < 0)
            {
                expansion.Append("-");
                numerator = BigInteger.Abs(Numerator).ToString();
            }

            BigInteger carry = 0;
            bool firstDigitComputed = false;

            Action<Number> computeDigit = (Number item) =>
            {
                BigInteger result = carry / item.Denominator;
                carry = 10 * (carry % item.Denominator);

                firstDigitComputed |= result > 0;

                if(firstDigitComputed)
                    expansion.Append(result.ToString());
            };

            foreach(char item in numerator)
            {
                carry += BigInteger.Parse(Convert.ToString(item));
                computeDigit(this);
            }

            if (!firstDigitComputed)
            {
                expansion.Append("0");
            }

            if (carry > 0)
            {
                expansion.Append(".");

                for (int i = 0; i < decimalPoints && carry > 0; i++)
                {
                    computeDigit(this);
                }
            }

            return expansion.ToString();
        }

        public override bool Equals(object obj)
        {
            if (obj is Number)
            {
                Number item = (Number)obj;

                return item.Numerator == this.Numerator && item.Denominator == this.Denominator;
            }
            else
            {
                return false;
            }
        }

        public override int GetHashCode()
        {
            return Numerator.GetHashCode() ^ Denominator.GetHashCode();
        }
    }
}
